package iagovsothello.ai;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Iterator;
import iagovsothello.core.GameState;
import iagovsothello.core.Move;
import iagovsothello.ai.movesort.*;
import iagovsothello.core.Position;
import iagovsothello.logger.Entry;
import iagovsothello.logger.Log;

public class ABPruning extends TreeSearchAgent {

	public static final String name = "ABPruning";
	private static final long serialVersionUID = -8752333284315759818L;
	
	public ABPruning() 
	{
		super();
	}

	public ABPruning(int playerNumber) 
	{
		super(playerNumber);
	}

	public ABPruning(int playerNumber, boolean debugMode) 
	{
		super(playerNumber, debugMode);
	}

	public ABPruning(int playerNumber, boolean debugMode, Log log) 
	{
		super(playerNumber, debugMode, log);
	}

	@Override
	public String getName()
	{
		return name;
	}

	@Override
	public TreeSearchAgent clone() 
	{
		ABPruning copy = new ABPruning();
		copyTreeSearchAgentAttributes(copy);
		return copy;
	}
	
	public String toString()
	{
		return name;
	}
	
	protected int exploreNode(Node node, int depth, Entry e)
	{
		return exploreNode(node,Integer.MIN_VALUE,Integer.MAX_VALUE,0,e);
	}
	
	private int exploreNode(Node node, int alpha, int beta, int depth, Entry e)
	{
//		for (int i = 0; i < node.state.getTurnNumber(); i++)
//		{
//			System.out.print("-");
//		}
//		System.out.println(node.state.getTurnNumber()+": "+node.state);
		
		List<Move> list = node.state.getLegalMoves();
		if (list.isEmpty() && node.state.isGameOver())
		{
			if (e != null && e.maxDepth < depth)
				e.maxDepth = depth;
			node.terminal = true;
			node.sequence = new LinkedList<Position>();
			node.sequence.add(node.move);
			return utilityValue(node.state);
		}
		else
		{
			//controllo in tabella hash
			if (hashTable != null)
			{
				Integer v = getFromTable(node.state);
				if (v != null)
				{
					return v;
				}
			}
			
			/*se questo nodo non possiede figli, allora vengono creati esplorando le mosse disponibili, altrimenti si prosegue con i figli già presenti*/
			if (node.children == null)
			{
				node.children = new LinkedList<Node>();
				if (list.isEmpty())
				{
					node.children.add(new Node(node.state.cloneAndMove(null),null));
				}
				else
				{
					//si ordina la lista delle mosse disponibili in ordine decrescente per punteggio fornito dalla mossa
					Collections.sort(list,new MovePointsComparator());
					
					for (Move m : list)
					{
						node.children.add(new Node(node.state.cloneAndMove(m.getPosition()),m.getPosition()));
						//System.out.println("to string " + p.toString());
	
					}
				}
			}
			if (e != null)
			{
//				e.nodes += node.children.size();
				if (depth == 0)
					e.branchingFactor = node.children.size();
			}
			
			//valore minimax del nodo
			int v;
			
			//se il nodo corrente è un nodo MAX
			if (node.state.isMyTurn(playerNumber))
			{
				v = maxValue(node,alpha,beta,depth,e);
			}
			//se il nodo corrente è invece un nodo MIN
			else
			{
				v = minValue(node,alpha,beta,depth,e);
			}
			
			//se la tabella esiste e si può aggiungere un nuovo record, si aggiunge in tabella 
			if (hashTable != null && this.hashTableRewriting && hashTable.size() < hashTableMaxDimension)
			{
				this.updateTable(node.state, v);
			}
			return v;
		}	
	}
	
	private int maxValue(Node node, int alpha, int beta, int depth, Entry e)
	{
		Iterator<Node> childrenIterator = node.children.iterator();
		LinkedList<Position> optimumSequence = null;
		
		int value = Integer.MIN_VALUE;
		boolean stop = false;
		while (childrenIterator.hasNext() && !stop)
		{
			Node n = childrenIterator.next();
			//System.out.println("stampa valore figli:" + n.minimaxValue);
			
			int r = exploreNode(n,alpha,beta,depth+1,e);
			value = Math.max(value, r);
			
			if (value > beta)
				stop = true;
			else
			{
				if (value > alpha)
				{
					alpha = value;
					optimumSequence = n.sequence;
				}
			}
			
			/*dopo che per ciascun nodo è stato calcolato ricorsivamente il valore minimax, se ne distruggono tutti i figli
			 * col metodo childrenDestroy() per evitare consumi inutili di spazio*/
			n.childrenDestroy();
		}
		if (optimumSequence == null)
		{
			node.sequence = new LinkedList<Position>();
			node.sequence.add(node.move);
		}
		else
		{
			node.sequence = optimumSequence;
			node.sequence.addFirst(node.move);
		}
		return value;
	}
	
	private int minValue(Node node, int alpha, int beta, int depth, Entry e)
	{
		Iterator<Node> childrenIterator = node.children.iterator();
		LinkedList<Position> optimumSequence = null;
		
		int value = Integer.MAX_VALUE;
		boolean stop = false;
		while (childrenIterator.hasNext() && !stop)
		{
			Node n = childrenIterator.next();
			//System.out.println("stampa valore figli:" + n.minimaxValue);
			
			int r = exploreNode(n,alpha,beta,depth+1,e);
			value = Math.min(value, r);
			
			if (value < alpha)
				stop = true;
			else
			{
				if (value < beta)
				{
					beta = value;
					optimumSequence = n.sequence;
				}
			}
			
			/*dopo che per ciascun nodo è stato calcolato ricorsivamente il valore minimax, se ne distruggono tutti i figli
			 * col metodo childrenDestroy() per evitare consumi inutili di spazio*/
			n.childrenDestroy();
		}
		if (optimumSequence == null)
		{
			node.sequence = new LinkedList<Position>();
			node.sequence.add(node.move);
		}
		else
		{
			node.sequence = optimumSequence;
			node.sequence.addFirst(node.move);
		}
		return value;
	}
}
